package org.movee.net.cfg.parser.service;

import org.movee.net.cfg.parser.antlr.IndentedRowCfgLexer;
import org.movee.net.cfg.parser.antlr.IndentedRowCfgParser;
import org.movee.net.cfg.parser.antlr.JuniperCfgLexer;
import org.movee.net.cfg.parser.antlr.JuniperCfgParser;
import org.movee.net.cfg.parser.antlr.XorplusCfgLexer;
import org.movee.net.cfg.parser.antlr.XorplusCfgListenerImpl;
import org.movee.net.cfg.parser.antlr.CfgErrorListener;
import org.movee.net.cfg.parser.antlr.IndentedRowCfgListenerImpl;
import org.movee.net.cfg.parser.antlr.JuniperCfgListenerImpl;
import org.movee.net.cfg.parser.antlr.XorplusCfgParser;
import org.movee.net.cfg.parser.config.NetCfgProperties;
import org.movee.net.cfg.parser.domain.api.ApiException;
import org.movee.net.cfg.parser.domain.api.ApiStatusCode;
import org.movee.net.cfg.parser.domain.model.ConfigModel;
import org.movee.net.cfg.parser.domain.parser.IndentedRowConfigNode;
import org.movee.net.cfg.parser.parser.brocade.BrocadeCfgParseTreeWalker;
import org.movee.net.cfg.parser.parser.brocade.BrocadeConfigModelConverter;
import org.movee.net.cfg.parser.parser.cisco.CiscoCfgParseTreeWalker;
import org.movee.net.cfg.parser.parser.cisco.CiscoConfigModelConverter;
import org.movee.net.cfg.parser.parser.h3c.H3cCfgParseTreeWalker;
import org.movee.net.cfg.parser.parser.h3c.H3cConfigModelConverter;
import org.movee.net.cfg.parser.parser.huawei.HuaweiCfgParseTreeWalker;
import org.movee.net.cfg.parser.parser.huawei.HuaweiConfigModelConverter;
import org.movee.net.cfg.parser.parser.juniper.JuniperConfigModelConverter;
import org.movee.net.cfg.parser.parser.ruijie.RuijieCfgParseTreeWalker;
import org.movee.net.cfg.parser.parser.ruijie.RuijieConfigModelConverter;
import org.movee.net.cfg.parser.parser.xorplus.XorplusConfigModelConverter;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.DiagnosticErrorListener;
import org.antlr.v4.runtime.Parser;
import org.antlr.v4.runtime.atn.PredictionMode;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.io.File;
import java.io.IOException;
import java.util.Objects;

/**
 *
 *
 * @author 
 */
@Component
@Slf4j
public class ConfigFileParser {

    private final NetCfgProperties nconfProperties;
    private final CfgErrorListener cfgErrorListener = new CfgErrorListener();
    private final DiagnosticErrorListener diagErrorListener = new DiagnosticErrorListener();
    private final ObjectMapper jackson = new ObjectMapper();

    @Autowired
    public ConfigFileParser(NetCfgProperties nconfProperties) {
        this.nconfProperties = nconfProperties;
    }

    public Object parseConfigFile(String mgmtIp, String iaz) {

        File target = null;
        if (StringUtils.hasText(iaz)) {
            String basePath = nconfProperties.getConfBasePath() + "/" + iaz.toLowerCase();
            File file = new File(basePath);
            target = findTargetConfigFile(file, mgmtIp);
        }

        if (target == null) {
            String basePath = nconfProperties.getConfBasePath();
            File file = new File(basePath);
            target = findTargetConfigFile(file, mgmtIp);
        }

        if (target == null) {
            String msg = String.format("configuration file is not found. device mgmtIP: %s, iaz: %s",
                    mgmtIp, iaz == null ? "" : iaz);
            throw new ApiException(ApiStatusCode.RESOURCE_NOT_FOUND, msg);
        }

        return parseConfigFile(mgmtIp, target);
    }

    public Object parseConfigFile(String mgmtIp, File file) {

        try {
            String fileName = file.getName();
            log.info("fileName: {}", fileName);

            if (fileName.endsWith(".json")) {
                return parseSonicConfigFile(mgmtIp, file);
            }

            String vendor = fileName.split("_")[1].split("\\.")[0].toLowerCase();
            switch (vendor) {
                case "huawei": {
                    return parseHuaweiConfigFile(mgmtIp, file);
                }
                case "huarong": {
                    return parseHuarongConfigFile(mgmtIp, file);
                }
                case "h3c": {
                    return parseH3cConfigFile(mgmtIp, file);
                }
                case "ruijie": {
                    return parseRuijieConfigFile(mgmtIp, file);
                }
                case "xorplus": {
                    return parseXorplusConfigFile(mgmtIp, file);
                }
                case "libra": {
                    return parseSonicConfigFile(mgmtIp, file);
                }
                case "cisco": {
                    return parseCiscoConfigFile(mgmtIp, file);
                }
                case "juniper": {
                    return parseJuniperConfigFile(mgmtIp, file);
                }
                case "brocade": {
                    return parseBrocadeConfigFile(mgmtIp, file);
                }
                default: {
                    throw new ApiException(ApiStatusCode.INVALID_QUERY_PARAM,
                            "do not find associated configuration file");
                }
            }
        } catch (IOException e) {
            throw new ApiException(ApiStatusCode.INVALID_QUERY_PARAM,
                    "do not find configuration file");
        }

    }

    public Object parseSonicConfigFile(String mgmtIp, File file) {

        try {
            return jackson.readTree(file);
        } catch (IOException e) {
            throw new ApiException(ApiStatusCode.INVALID_QUERY_PARAM, "read configuration file failed");
        }

    }


    public ConfigModel parseXorplusConfigFile(String mgmtIp, File file) throws IOException {
        XorplusCfgLexer lexer = new XorplusCfgLexer(CharStreams.fromFileName(file.getAbsolutePath()));
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        XorplusCfgParser parser = new XorplusCfgParser(tokens);
        addErrorListener(parser);
        ParseTreeWalker walker = new ParseTreeWalker();
        XorplusCfgListenerImpl listener = new XorplusCfgListenerImpl(mgmtIp, "Xorplus");
        walker.walk(listener, parser.file());

        return new XorplusConfigModelConverter().convert(listener.getModel());
    }

    public ConfigModel parseJuniperConfigFile(String mgmtIp, File file) throws IOException {
        JuniperCfgLexer lexer = new JuniperCfgLexer(CharStreams.fromFileName(file.getAbsolutePath()));
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        JuniperCfgParser parser = new JuniperCfgParser(tokens);
        addErrorListener(parser);
        ParseTreeWalker walker = new ParseTreeWalker();
        JuniperCfgListenerImpl listener = new JuniperCfgListenerImpl(mgmtIp, "Juniper");
        walker.walk(listener, parser.file());

        log.info("parseJuniperConfigFile: {}", jackson.writeValueAsString(listener.getModel()));

        return new JuniperConfigModelConverter().convert(listener.getModel());
    }

    public ConfigModel parseHuaweiConfigFile(String mgmtIp, File file) throws IOException {

        IndentedRowConfigNode configNodeTree = parseIndentedRowConfigFile(file, "#");

        HuaweiCfgParseTreeWalker walker = new HuaweiCfgParseTreeWalker(mgmtIp, "Huawei");
        walker.walk(configNodeTree);

        return new HuaweiConfigModelConverter().convert(walker.getModel());
    }

    public ConfigModel parseHuarongConfigFile(String mgmtIp, File file) throws IOException {

        IndentedRowConfigNode configNodeTree = parseIndentedRowConfigFile(file, "#");

        HuaweiCfgParseTreeWalker walker = new HuaweiCfgParseTreeWalker(mgmtIp, "Huarong");
        walker.walk(configNodeTree);

        return new HuaweiConfigModelConverter().convert(walker.getModel());
    }

    public ConfigModel parseH3cConfigFile(String mgmtIp, File file) throws IOException {
        IndentedRowConfigNode configNodeTree = parseIndentedRowConfigFile(file, "#");

        H3cCfgParseTreeWalker walker = new H3cCfgParseTreeWalker(mgmtIp, "H3c");
        walker.walk(configNodeTree);

        return new H3cConfigModelConverter().convert(walker.getModel());
    }

    public ConfigModel parseRuijieConfigFile(String mgmtIp, File file) throws IOException {
        IndentedRowConfigNode configNodeTree = parseIndentedRowConfigFile(file, "!");

        RuijieCfgParseTreeWalker walker = new RuijieCfgParseTreeWalker(mgmtIp, "Ruijie");
        walker.walk(configNodeTree);

        return new RuijieConfigModelConverter().convert(walker.getModel());
    }

    public ConfigModel parseCiscoConfigFile(String mgmtIp, File file) throws IOException {
        IndentedRowConfigNode configNodeTree = parseIndentedRowConfigFile(file, "#");

        CiscoCfgParseTreeWalker walker = new CiscoCfgParseTreeWalker(mgmtIp, "Cisco");
        walker.walk(configNodeTree);

        return new CiscoConfigModelConverter().convert(walker.getModel());
    }

    public ConfigModel parseBrocadeConfigFile(String mgmtIp, File file) throws IOException {

        IndentedRowConfigNode configNodeTree = parseIndentedRowConfigFile(file, "#");

        BrocadeCfgParseTreeWalker walker = new BrocadeCfgParseTreeWalker(mgmtIp, "Brocade");
        walker.walk(configNodeTree);

        return new BrocadeConfigModelConverter().convert(walker.getModel());
    }

    private IndentedRowConfigNode parseIndentedRowConfigFile(File file, String sectionSplitter) throws IOException {
        IndentedRowCfgLexer lexer = new IndentedRowCfgLexer(CharStreams.fromFileName(file.getAbsolutePath()));
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        IndentedRowCfgParser parser = new IndentedRowCfgParser(tokens);
        addErrorListener(parser);
        ParseTreeWalker walker = new ParseTreeWalker();
        IndentedRowCfgListenerImpl listener = new IndentedRowCfgListenerImpl(sectionSplitter);
        walker.walk(listener, parser.file());

        return listener.getParseTree();
    }

    private File findTargetConfigFile(File file, String mgmtIp) {

        if (file.isFile()) {
            String fileName = file.getName();
            if (Objects.equals(fileName.split("_")[0], mgmtIp)) {
                return file;
            }
        } else if (file.isDirectory()) {
            File[] subFiles = file.listFiles();
            if (subFiles != null && subFiles.length > 0) {
                for (File subFile : subFiles) {
                    File candidate = findTargetConfigFile(subFile, mgmtIp);
                    if (candidate != null) {
                        return candidate;
                    }
                }
            }
        }

        return null;
    }

    private void addErrorListener(final Parser parser) {
        parser.removeErrorListeners();
        parser.addErrorListener(cfgErrorListener);
        parser.addErrorListener(diagErrorListener);
        parser.getInterpreter().setPredictionMode(PredictionMode.LL_EXACT_AMBIG_DETECTION);
    }

}
